<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" tip_dom="width=device-width, initial-scale=1.0">
  <title>累进 range 加载</title>
</head>
<body>
  <div id="tip_dom"></div>
  <div id="description"></div>
  <video width="500px" height="500px" id="video_ins" autoplay controls />

</body>
  <!-- <script src="https://cdn.jsdelivr.net/npm/opfs-tools-explorer"></script> -->
  <script>
    // OTExplorer.init();
  </script>
  <script type="text/javascript">
    /**
     * 满足条件：
     * · 如非同源，服务端支持跨域
     * · 服务端支持 range 切片
     * · 浏览器支持 opfs 相关 api
     * 
     *  TODO: 目前有点小小问题：fetch 获取的分片是 blob，存进 opfs 后，读取出来是 File。因为 File 继承了 blob ，所以目前没发现异常。
    */
    var url = `//obs.ispm.vip/animation-hw/f65840bc6455b38854c2da2ff96f661b.mp4`
    const supportFlag = navigator.storage?.getDirectory && navigator.storage.estimate 
    const tip_dom = document.getElementById('tip_dom')
    var index = 0
 
    const mimeCodec = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"' // ./mp4info frag_bunny.mp4 | grep Codec
    const slice_size = 5 * 1000 * 1000
    const mediaSource = new MediaSource();
    const folderName = encodeURIComponent(url)
    var opfsRoot, 
    sliceFolder, 
    sourceBuffer,
    fileSizeInBytes;
    const chunks = []
    const prevFileMap = {}

    if(!supportFlag) {
      tip_dom.innerText = '不支持 OPFS '
      video_ins.src = url
      throw new Error(tip_dom.innerText)
    } 
    

    async function core() {
      
      await getFileSize()
      tip_dom.innerText = ' start '
      opfsRoot = await navigator.storage.getDirectory()
      sliceFolder = await opfsRoot.getDirectoryHandle(folderName, { create: true })
      for await (const [key, value] of sliceFolder.entries()) {
        const file = await value.getFile()
        const arrayBuffer = await file.arrayBuffer();
        prevFileMap[key] = arrayBuffer
      }

      video_ins.src = URL.createObjectURL(mediaSource);
      mediaSource.addEventListener('sourceopen', () => {
        console.log('mediaSource sourceopen')
        sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
        sourceBuffer.addEventListener('error', (e) => {
          console.log('sourceBuffer error', e)
        })
        sourceBuffer.addEventListener('updateend', () => {
          console.log('Buffered ranges:', sourceBuffer.updating, sourceBuffer.buffered);
        })
        getSlices()
      })
      mediaSource.addEventListener('error', (e) => {
        console.log('mediaSource error', e)
      })
    }

    async function getFileSize() {
      const response = await fetch(url,  { method: 'HEAD' })
      const contentLength = response.headers.get('Content-Length')
      if (contentLength) {
        fileSizeInBytes = parseInt(contentLength)
      }
    }

    async function writeDescription() {
      const info = await navigator.storage.estimate()
      const tmp = {
        '占用率':`${(info.usage/info.quota * 100).toFixed(2)}%`,
        '已使用': `${Math.floor(info.usage/1000/1000)}MB`,
        '可用容量': `${Math.floor(info.quota/1000/1000)}MB`,
        '文件大小（字节）': `${Math.floor(fileSizeInBytes/1000/1000)}MB`,
      }
      const description = document.getElementById('description')
      description.innerText = JSON.stringify(tmp, null, 2)
    }
    
    async function getSlices() {
      let start = index
      end = start + slice_size
      end = end >= fileSizeInBytes ? '' : end
      const bytes = `${start}-${end}`
      var sliceFileHandle = await sliceFolder.getFileHandle(bytes, { create: true })
      
      if(prevFileMap[bytes]) {
        chunks.push(prevFileMap[bytes])
      } else {
        const res = await fetch(url, {
          headers: { 
            range: `bytes=${bytes}`
          }
        }).then(res => res.arrayBuffer())
        prevFileMap[bytes] = res
        chunks.push(res)
        const writable = await sliceFileHandle.createWritable()
        await writable.write(res)
        await writable.close()
      }
      sourceBuffer.appendBuffer(prevFileMap[bytes]);
      index = end + 1
      writeDescription()
    }

    core()
    
  </script>
</html>